﻿using iPanel.Otlp.Storage;
using Microsoft.FluentUI.AspNetCore.Components.Extensions;
using SqlSugar;

namespace iPanel.DataProvider;

public class ClearExpireBackgroundService : BackgroundService
{
    private readonly ILogger<ClearExpireBackgroundService> _logger;
    private readonly TelemetryDbContext telemetryDbContext;
    private readonly IOptions<DashboardOptions> dashboardOptions;
    private Timer _timer;

    public ClearExpireBackgroundService(ILogger<ClearExpireBackgroundService> logger, TelemetryDbContext telemetryDbContext, IOptions<DashboardOptions> dashboardOptions)
    {
        _logger = logger;
        this.telemetryDbContext = telemetryDbContext;
        this.dashboardOptions = dashboardOptions;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 计算距离下一个凌晨一点的时间间隔
        var now = DateTime.Now;
        var nextMidnightOne = now.Date.AddDays(1).AddHours(1);
        if (now > nextMidnightOne)
        {
            nextMidnightOne = nextMidnightOne.AddDays(1);
        }
        var delay = (int)(nextMidnightOne - now).TotalMilliseconds;

        // 创建定时器，在计算好的延迟后触发，之后每天触发一次
        _timer = new Timer(DoWork, null, delay, (int)TimeSpan.FromDays(1).TotalMilliseconds);

        // 注册取消令牌的回调，在服务停止时停止定时器
        stoppingToken.Register(() => _timer?.Dispose());

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        var atLogInfo = DateTime.Now.AddDays(-dashboardOptions.Value.TelemetryLimits.LogInfoSaveDay);
        var atLogWarn = DateTime.Now.AddDays(-dashboardOptions.Value.TelemetryLimits.LogWarningSaveDay);
        var atLogError = DateTime.Now.AddDays(-dashboardOptions.Value.TelemetryLimits.LogErrorSaveDay);
        var atTraceInfo = DateTime.Now.AddDays(-dashboardOptions.Value.TelemetryLimits.TraceInfoSaveDay);
        var atTraceError = DateTime.Now.AddDays(-dashboardOptions.Value.TelemetryLimits.TraceErrorSaveDay);
        try
        {
            _logger.LogInformation("开始执行每天凌晨一点的任务, at {0}", DateTime.Now);

            // log
            {
                var delete = telemetryDbContext.Db
                    .Deleteable<OtlpLogEntryEntity>()
                    .Where(_ =>
                                (_.Severity < LogLevel.Information && _.TimeStamp <= atLogInfo) ||
                                (_.Severity == LogLevel.Warning && _.TimeStamp <= atLogWarn) ||
                                (_.Severity >= LogLevel.Error && _.TimeStamp <= atLogError)
                    );
                _logger.LogInformation("Del log sql is {0}, params is {1}", delete.ToSql().Key,
                    string.Join(';', delete.ToSql().Value.Select(_ => string.Format("{0}:{1}", _.ParameterName, _.Value)))
                    );
                delete.ExecuteCommand();
            }

            // trace
            {
                var delete = telemetryDbContext.Db
                    .Deleteable<OtlpTraceEntity>()
                    .Where(_ => (_.CreateAt <= atTraceInfo && SqlFunc.Subqueryable<OtlpSpanEntity>().Where(s => s.TraceId == _.TraceId && s.Status != OtlpSpanStatusCode.Error).Any())
                    || _.CreateAt <= atTraceError
                );
                _logger.LogInformation("Del log trace is {0}, params is {1}", delete.ToSql().Key,
                    string.Join(';', delete.ToSql().Value.Select(_ => string.Format("{0}:{1}", _.ParameterName, _.Value))));
                delete.ExecuteCommand();
            }

            // span events
            {
                var delete = telemetryDbContext.Db
                    .Deleteable<OtlpSpanEventEntity>()
                    .Where(_ => (_.Time <= atTraceInfo && SqlFunc.Subqueryable<OtlpSpanEntity>().Where(s => s.TraceId == _.TraceId && s.Status != OtlpSpanStatusCode.Error).Any())
                    || _.Time <= atTraceError
                    );
                _logger.LogInformation("Del log span events is {0}, params is {1}", delete.ToSql().Key,
                    string.Join(';', delete.ToSql().Value.Select(_ => string.Format("{0}:{1}", _.ParameterName, _.Value))));
                delete.ExecuteCommand();
            }

            // span links
            {
                var delete = telemetryDbContext.Db
                    .Deleteable<OtlpSpanLinkEntity>()
                    .Where(_ => (_.CreateAt <= atTraceInfo && SqlFunc.Subqueryable<OtlpSpanEntity>().Where(s => s.TraceId == _.TraceId && s.Status != OtlpSpanStatusCode.Error).Any())
                    || _.CreateAt <= atTraceError
                    );
                _logger.LogInformation("Del log span links is {0}, params is {1}", delete.ToSql().Key,
                    string.Join(';', delete.ToSql().Value.Select(_ => string.Format("{0}:{1}", _.ParameterName, _.Value))));
                delete.ExecuteCommand();
            }

            // span
            {
                var delete = telemetryDbContext.Db
                    .Deleteable<OtlpSpanEntity>()
                    .Where(_ => (_.StartTime <= atTraceInfo && _.Status != OtlpSpanStatusCode.Error)
                    || _.StartTime <= atTraceError
                    );
                _logger.LogInformation("Del log span is {0}, params is {1}", delete.ToSql().Key,
                    string.Join(';', delete.ToSql().Value.Select(_ => string.Format("{0}:{1}", _.ParameterName, _.Value))));
                delete.ExecuteCommand();
            }

            // 执行 VACUUM 命令来收缩数据库
            {
                //REINDEX `OtlpLogEntries`; 重建索引    
                telemetryDbContext.Db.Ado.ExecuteCommand("VACUUM;");
                _logger.LogInformation("SQLite VACUUM;");
            }
            _logger.LogInformation("每天凌晨一点的任务执行完成, at {0}", DateTime.Now);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "执行任务时发生错误");
        }
    }

    public override void Dispose()
    {
        _timer?.Dispose();
        base.Dispose();
    }
}
